package pl.edu.mimuw.irs.core.model.design.processing;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import pl.edu.mimuw.irs.core.util.NamingTool;
import pl.edu.mimuw.irs.rcp.util.Pair;


/**
 * @author Jakub Rauch
 * Created on: 2009-05-07
 */
public class Resource implements Serializable {

	private static final long serialVersionUID = -6786426056173104308L;
	
	// *** Attributes

	private String name;
	// Set of custom roles that an actor is capable of acting.
	private Map<Role, Integer> rolesMap;
	private List<RoleInstance> rolesInstances;
	private int unitCost;
	// Getters and setters
	public String getName() {
		if (this.name == null) this.name = createNewName();
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Map<Role, Integer> getRolesMap() {
		if (this.rolesMap == null) this.rolesMap = new HashMap<Role, Integer>();
		return Collections.unmodifiableMap(this.rolesMap);
	}
	public List<RoleInstance> getRolesInstances() {
		if (rolesInstances == null) rolesInstances = new ArrayList<RoleInstance>();
		return Collections.unmodifiableList(rolesInstances);
	}
	public void setRoles(Collection<RoleInstance> roles) {
		getRolesMap();
		getRolesInstances();
		rolesMap.clear();
		rolesInstances.clear();
		for (RoleInstance role : roles) {
			Integer count = rolesMap.get(role.getRole());
			if (count == null) {
				count = 0;
			}
			rolesMap.put(role.getRole(), count + 1);
			rolesInstances.add(role);
		}
	}
	public void addRoleInstance(RoleInstance roleInstance) {
		Integer count = getRolesMap().get(roleInstance.getRole());
		if (count == null) count = 0;
		rolesMap.put(roleInstance.getRole(), count + 1);
		getRolesInstances().add(roleInstance);
	}
	public void removeRoleInstance(RoleInstance roleInstance) {
		Integer count = getRolesMap().get(roleInstance.getRole());
		if (count != null) {
			if (count > 1) {
				rolesMap.put(roleInstance.getRole(), count - 1);
			} else {
				rolesMap.remove(roleInstance.getRole());
			}
		}
		getRolesInstances().remove(roleInstance);
	}
	public int getUnitCost() {
		return unitCost;
	}
	
	// *** Constructors
	
	public Resource(String name, Set<RoleInstance> rolesInstances) {
		setRoles(rolesInstances);
		setName(name);
		this.unitCost = rolesInstances.size(); // TODO : unitCost Handling
	}
	
	public Resource(Set<RoleInstance> rolesInstances) {
		this(null, rolesInstances);
	}
	
	// *** Methods
	
	private String createNewName() {
		StringBuffer nameBuffer = new StringBuffer();
		nameBuffer.append("Res");
		if (getRolesInstances().size() > 0) {
			nameBuffer.append("[");
			for (RoleInstance instance : getRolesInstances()) {
				nameBuffer.append(NamingTool.abbreviation(instance.toString()));
				nameBuffer.append(";");
			}
			nameBuffer.replace(nameBuffer.lastIndexOf(";"), 
					nameBuffer.length(), "]");
		}
		return nameBuffer.toString();
	}
	
	/**
	 * Actor meets requirement if he is capable of playing every role of this requirement.
	 */
	public int meets(Requirement requirement) {
		/*
		 * Pair(REQUIRED, AVAILABLE)
		 */
		Map<Role, Pair<Integer, Integer>> map = new LinkedHashMap<Role, Pair<Integer, Integer>>();
		// XXX : Efficiency?
		for (Entry<Role, Integer> entry : requirement.getRolesMap().entrySet()) {
			map.put(entry.getKey(), new Pair<Integer, Integer>(entry.getValue(), 0));
		}
		for (Entry<Role, Integer> entry : getRolesMap().entrySet()) {
			Pair<Integer, Integer> pair = map.get(entry.getKey());
			if (pair != null) {
				pair.setValue(entry.getValue());
			}
		}
		int min = Integer.MAX_VALUE;
		for (Pair<Integer, Integer> pair : map.values()) {
			/*
			 * Key should be always greater than zero
			 */
			int times = pair.getValue() / pair.getKey();
			if (times == 0) return 0;
			else if (min > times) min = times;
		}
		return min;
	}
	
	/**
	 * Works on requirement. Returns time taken - depends on cost of requirement and 
	 * skills of this actor. Assumes this actor meets this requirement.
	 * @param requirement
	 * @return
	 */
	public double work(Requirement requirement) {
		double timeUnits = 0;
		for (RoleInstance roleInstance : requirement.getRolesInstances()) {
			timeUnits = roleInstance.getUnits();
		}
		
		return timeUnits;
	}
	
	@Override
	public String toString() {
		return getName();
	}
}
